import {
  defined,
  Entity,
  KeyboardEventModifier,
  ScreenSpaceEventHandler,
  ScreenSpaceEventType,
  Viewer,
} from "cesium";
import { defaultConfig, getViewer, nextTick, isMap, isSet } from "yt-cesium";

const { eventConfig } = defaultConfig;
type EventConfigType = {
  viewerName?: string;
  isEntity?: boolean;
};

type EventMapType = Map<string, Set<CustomFunction>>;

export const enum PosType {
  POS_ENTITY = "entity",
  POS_POINT = "point",
}

export type EventWeakMap = WeakMap<Viewer, EventWeakMapValue>;
export type EventWeakMapValue = EventMapType | Set<CustomFunction>;

export type PositionEventType = ScreenSpaceEventHandler.PositionedEvent;

export interface CustomFunction extends Function {
  _isEntity?: boolean;
}

function setEventToMap(
  fn: CustomFunction,
  viewerEventMap: EventMapType | Set<CustomFunction>,
  posType: PosType
) {
  if (isSet(viewerEventMap)) {
    viewerEventMap.add(fn);
    return;
  }
  if (!viewerEventMap.has(posType)) {
    viewerEventMap.set(posType, new Set());
  }
  viewerEventMap.get(posType)?.add(fn);
}

export function generateClose(
  fn: CustomFunction,
  mapValue: EventWeakMapValue,
  isEntity: boolean
) {
  if (isSet(mapValue)) {
    mapValue?.delete(fn);
    return;
  }
  if (isEntity) {
    mapValue?.get(PosType.POS_ENTITY)?.delete(fn);
  }
  mapValue?.get(PosType.POS_POINT)?.delete(fn);
}

/**
 * 创建事件注册方法
 * @param eventMap
 */
export function createEventRegister(eventMap: EventWeakMap) {
  return function (fn: CustomFunction, opt: EventConfigType = {}) {
    const { viewerName, isEntity = false } = opt;
    let _viewer: Viewer;
    nextTick().then((viewer: any) => {
      _viewer = viewerName ? getViewer(viewerName) : viewer;
      if (!eventMap.has(viewer)) {
        eventMap.set(viewer, new Map());
      }
      const viewerEvent = eventMap.get(viewer)!;
      // 实体事件和 空白区域事件 放入不同的Set中
      if (isEntity) {
        setEventToMap(fn, viewerEvent, PosType.POS_ENTITY);
        return;
      }
      setEventToMap(fn, viewerEvent, PosType.POS_POINT);
    });
    return () => {
      const viewerEvent = eventMap.get(_viewer);
      if (!viewerEvent) return;
      generateClose(fn, viewerEvent, isEntity);
    };
  };
}

// 区分 单击事件和双击事件
let eventInterval: number;

/**
 * 创建事件执行操作手柄
 * @param eventMap
 * @param eventType
 * @param modifier
 */
export function createEventHandler(
  eventMap: EventWeakMap,
  eventType: ScreenSpaceEventType,
  modifier?: KeyboardEventModifier
) {
  return function (viewer: Viewer, handler?: ScreenSpaceEventHandler) {
    if (!handler) handler = viewer.screenSpaceEventHandler;

    /**
     * 事件执行函数
     * @param event
     */
    const eventExecute = (event: PositionEventType) => {
      const eventList = eventMap.get(viewer);
      if (!eventList) return;
      // 实体事件
      if (isEntityByEvent(event)) {
        const _eventRes = isMap(eventList)
          ? eventList.get(PosType.POS_ENTITY)
          : eventList;

        _eventRes?.forEach((fn) => {
          fn(getEntityByEvent(event));
        });
        return;
      }
      const _eventRes = isMap(eventList)
        ? eventList.get(PosType.POS_POINT)
        : eventList;
      // 其他事件(空白区域)
      _eventRes?.forEach((fn) => {
        fn(event);
      });
    };

    handler.setInputAction(
      (event: PositionEventType) => {
        clearInterval(eventInterval);
        // 解决 双击事件 触发单击事件问题
        if (eventType === ScreenSpaceEventType.LEFT_CLICK) {
          eventInterval = window.setTimeout(() => {
            eventExecute(event);
          }, eventConfig.clickAndDbClickInterval);
          return;
        }
        eventExecute(event);
      },
      eventType,
      modifier
    );
  };
}

/**
 * 判断当前鼠标点击的点是否是entity
 * @param event
 */
export function isEntityByEvent(event: PositionEventType): boolean {
  const viewer = getViewer();
  if (!viewer) return false;
  const pick = viewer.scene.pick(event.position);
  return defined(pick) && !!pick.id;
}

/**
 * 根据事件对象获取 entity
 * @param event
 */
export function getEntityByEvent(event: PositionEventType): Entity | undefined {
  const viewer = getViewer();
  if (!viewer) return;
  const pick = viewer.scene.pick(event.position);
  return defined(pick) && pick.id;
}
